package bancosys.tec.services.api;

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

import jmine.tec.services.api.ServiceAssertionFailedError;
import jmine.tec.services.api.ServiceValidationExecutionException;
import jmine.tec.services.api.ServicesAPIMessages;
import bancosys.tec.exception.CompoundMessageHolder;
import bancosys.tec.exception.LocalizedMessageHolder;
import bancosys.tec.exception.MessageHolder;
import bancosys.tec.persist.base.enumx.IntValuedEnum;
import bancosys.tec.persist.base.enumx.StringValuedEnum;
import bancosys.tec.utils.date.Date;

/**
 * Objeto responsável por fazer os asserts e guardar as mensagens de erro para que possam ser todas mostradas ao fim da execução de um
 * serviço de validação.
 * 
 * @author lundberg
 */
public class ServiceValidator {
    public static final String DEFAULT_BUNDLE = "jmine-tec-services-messages";

    public static final double DEFAULT_PRECISION = 0.001;

    public static final double VALOR_FINANCEIRO_PRECISION = 0.001;

    private final List<LocalizedMessageHolder> messages;

    /**
     * Construtor
     */
    public ServiceValidator() {
        this.messages = new LinkedList<LocalizedMessageHolder>();
    }

    /**
     * Joga uma exception de validação, caso tenham ocorrido erros.
     */
    public void throwValidationException() {
        if (!this.messages.isEmpty()) {
            throw new ServiceValidationExecutionException(ServicesAPIMessages.VALIDATION_VALIDATION_ERROR.create(new CompoundMessageHolder(
                    "\n").addMessages(this.messages)));
        }
    }

    /**
     * Comparação de BigDecimals (não lança erro)
     * 
     * @param expected valor esperado
     * @param got valor obtido
     * @return <code>true</code> se os valores forem considerados iguais, <code>false</code> caso contrário.
     */
    public boolean isEquals(BigDecimal expected, BigDecimal got) {
        return this.isEquals(expected, got, DEFAULT_PRECISION);
    }

    /**
     * Comparação de BigDecimals (não lança erro)
     * 
     * @param expected valor esperado
     * @param got valor obtido
     * @param precision tolerancia
     * @return <code>true</code> se os valores forem considerados iguais, <code>false</code> caso contrário.
     */
    public boolean isEquals(BigDecimal expected, BigDecimal got, double precision) {
        try {
            ServiceAssert.assertEquals(null, expected, got, precision);
            return true;
        } catch (ServiceAssertionFailedError e) {
            return false;
        }
    }

    /**
     * Comparação de BigDecimals exata
     * 
     * @param fieldName nome do campo
     * @param expected valor esperado
     * @param got valor obtido
     */
    public void assertEqualsExact(String fieldName, BigDecimal expected, BigDecimal got) {
        try {
            ServiceAssert.assertEquals(fieldName, expected, got, 0.0);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * @param fieldName nome do campo
     * @param expected valor esperado
     * @param got valor obtido
     * @param precision tolerancia
     */
    public void assertEquals(String fieldName, BigDecimal expected, BigDecimal got, double precision) {
        try {
            ServiceAssert.assertEquals(fieldName, expected, got, precision);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * @param fieldName nome do campo
     * @param expected valor esperado
     * @param got valor obtido
     */
    public void assertEquals(String fieldName, BigDecimal expected, BigDecimal got) {
        try {
            ServiceAssert.assertEquals(fieldName, expected, got, DEFAULT_PRECISION);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * @param fieldName nome do campo
     * @param expected valor esperado
     * @param got valor obtido
     */
    public void assertEquals(String fieldName, Date expected, Date got) {
        try {
            ServiceAssert.assertEquals(fieldName, expected, got);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * @param fieldName nome do campo
     * @param expected valor esperado
     * @param got valor obtido
     */
    public void assertEquals(String fieldName, IntValuedEnum expected, IntValuedEnum got) {
        try {
            ServiceAssert.assertEquals(fieldName, expected, got);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * @param fieldName nome do campo
     * @param expected valor esperado
     * @param got valor obtido
     */
    public void assertEquals(String fieldName, StringValuedEnum expected, StringValuedEnum got) {
        try {
            ServiceAssert.assertEquals(fieldName, expected, got);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * @param fieldName nome do campo
     * @param expected valor esperado
     * @param got valor obtido
     */
    public void assertEquals(String fieldName, Object expected, Object got) {
        try {
            ServiceAssert.assertEquals(fieldName, expected, got);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * Valida que a condição é verdadeira
     * 
     * @param fieldName nome do campo
     * @param condition valor obtido
     */
    public void assertTrue(String fieldName, boolean condition) {
        try {
            ServiceAssert.assertTrue(fieldName, condition);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * Valida que a condição é falsa.
     * 
     * @param fieldName nome do campo
     * @param condition valor obtido
     */
    public void assertFalse(String fieldName, boolean condition) {
        try {
            ServiceAssert.assertFalse(fieldName, condition);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * Valida que a referencia não é nula.
     * 
     * @param fieldName fieldName
     * @param obj referencia que deve ser validada como não nula
     */
    public void assertNotNull(String fieldName, Object obj) {
        try {
            ServiceAssert.assertNotNull(fieldName, obj);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * Valida que a referencia é nula.
     * 
     * @param fieldName fieldName
     * @param obj referencia que deve ser validada como nula
     */
    public void assertNull(String fieldName, Object obj) {
        try {
            ServiceAssert.assertNull(fieldName, obj);
        } catch (ServiceAssertionFailedError e) {
            this.messages.add(e.getLocalizedMessageHolder());
        }
    }

    /**
     * Registra uma falha.
     * 
     * @param bundleName nome do bundle contendo a mensagem
     * @param messageKey chave da mensagem
     * @param args argumentos
     * @deprecated use localized message holder
     */
    @Deprecated
    public void fail(String bundleName, String messageKey, Object... args) {
        this.fail(new MessageHolder(bundleName, messageKey, args));
    }

    /**
     * Registra uma falha.
     * 
     * @param message message
     */
    public void fail(LocalizedMessageHolder message) {
        this.messages.add(message);
    }

    /**
     * Formata um número em valor com locale pt.
     * 
     * @param value {@link BigDecimal}
     * @return número formatado.
     */
    public static String format(BigDecimal value) {
        if (value != null) {
            NumberFormat format = NumberFormat.getInstance(new Locale("pt"));
            int scale = value.scale() < 0 ? 0 : value.scale();
            format.setMinimumFractionDigits(scale);
            format.setMaximumFractionDigits(scale);
            return format.format(value);
        } else {
            return "";
        }
    }

    /**
     * Returns if there are validation errors in the service validator.
     * 
     * @return if there are validation errors in the service validator.
     */
    public boolean hasErrors() {
        return !this.messages.isEmpty();
    }
}
